/**************************************************************************************************
 * Filename:       BluetoothManager.java
 * Revised:        $Date: 2013-09-09 16:23:36 +0200 (ma, 09 sep 2013) $
 * Revision:       $Revision: 27674 $
 * <p/>
 * Copyright (c) 2013 - 2014 Texas Instruments Incorporated
 * <p/>
 * All rights reserved not granted herein.
 * Limited License.
 * <p/>
 * Texas Instruments Incorporated grants a world-wide, royalty-free,
 * non-exclusive license under copyrights and patents it now or hereafter
 * owns or controls to make, have made, use, import, offer to sell and sell ("Utilize")
 * this software subject to the terms herein.  With respect to the foregoing patent
 * license, such license is granted  solely to the extent that any such patent is necessary
 * to Utilize the software alone.  The patent license shall not apply to any combinations which
 * include this software, other than combinations with devices manufactured by or for TI (�TI Devices?.
 * No hardware patent is licensed hereunder.
 * <p/>
 * Redistributions must preserve existing copyright notices and reproduce this license (including the
 * above copyright notice and the disclaimer and (if applicable) source code license limitations below)
 * in the documentation and/or other materials provided with the distribution
 * <p/>
 * Redistribution and use in binary form, without modification, are permitted provided that the following
 * conditions are met:
 * <p/>
 * No reverse engineering, decompilation, or disassembly of this software is permitted with respect to any
 * software provided in binary form.
 * any redistribution and use are licensed by TI for use only with TI Devices.
 * Nothing shall obligate TI to provide you with source code for the software licensed and provided to you in object code.
 * <p/>
 * If software source code is provided to you, modification and redistribution of the source code are permitted
 * provided that the following conditions are met:
 * <p/>
 * any redistribution and use of the source code, including any resulting derivative works, are licensed by
 * TI for use only with TI Devices.
 * any redistribution and use of any object code compiled from the source code and any resulting derivative
 * works, are licensed by TI for use only with TI Devices.
 * <p/>
 * Neither the name of Texas Instruments Incorporated nor the names of its suppliers may be used to endorse or
 * promote products derived from this software without specific prior written permission.
 * <p/>
 * DISCLAIMER.
 * <p/>
 * THIS SOFTWARE IS PROVIDED BY TI AND TI�S LICENSORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL TI AND TI�S LICENSORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 **************************************************************************************************/
package com.example.llw.bluetooth;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

// import android.util.Log;

/**
 * Service for managing connection and data communication with a GATT server
 * hosted on a given Bluetooth LE device.
 */
public class BluetoothManager {
    static final String TAG = "BluetoothManager";
    /**
     * gatt action of ble connect
     */
    public final static String ACTION_GATT_CONNECTED = "com.example.ti.ble.common.ACTION_GATT_CONNECTED";
    /**
     * gatt action of ble disconnect
     */
    public final static String ACTION_GATT_DISCONNECTED = "com.example.ti.ble.common.ACTION_GATT_DISCONNECTED";
    /**
     * gatt action of ble discovered
     */
    public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.ti.ble.common.ACTION_GATT_SERVICES_DISCOVERED";
    /**
     * gatt action of ble read callback
     */
    public final static String ACTION_DATA_READ = "com.example.ti.ble.common.ACTION_DATA_READ";
    /**
     * gatt action of ble notify callback
     */
    public final static String ACTION_DATA_NOTIFY = "com.example.ti.ble.common.ACTION_DATA_NOTIFY";
    /**
     * gatt action of ble write callback
     */
    public final static String ACTION_DATA_WRITE = "com.example.ti.ble.common.ACTION_DATA_WRITE";
    /**
     * tag of data
     */
    public final static String EXTRA_DATA = "com.example.ti.ble.common.EXTRA_DATA";
    /**
     * tag of service or attribute 's uuid
     */
    public final static String EXTRA_UUID = "com.example.ti.ble.common.EXTRA_UUID";
    /**
     * tag of message 's status
     */
    public final static String EXTRA_STATUS = "com.example.ti.ble.common.EXTRA_STATUS";
    /**
     * tag of device 's address
     */
    public final static String EXTRA_ADDRESS = "com.example.ti.ble.common.EXTRA_ADDRESS";
    /**
     * Maximum Connection of ble
     */
    public static final int BLUETOOTH_CONNECT_NUMBER = 5;
    // BLE
    private Map<String, MyDevice> gattMap = new HashMap<String, MyDevice>();
    private android.bluetooth.BluetoothManager mBluetoothManager = null;
    private BluetoothAdapter mBtAdapter = null;
    //private BluetoothGatt mBluetoothGatt = null;
    private Map<String, WaitConnectDevice> waitConnectMap = new HashMap<String, WaitConnectDevice>();
    private Context mContext;
    private static BluetoothManager mThis = new BluetoothManager();

    private volatile boolean mBusy = false; // Write/read pending response
    private volatile boolean mConnectBusy = false; // connect pending response

    //private String mBluetoothDeviceAddress;
    private class WaitConnectDevice {
        private BluetoothDevice device;
        private boolean autoConnect;

        public WaitConnectDevice(BluetoothDevice device, boolean autoConnect) {
            this.device = device;
            this.autoConnect = autoConnect;
        }

        public BluetoothDevice getDevice() {
            return device;
        }

        public boolean getAutoConnect() {
            return autoConnect;
        }
    }

    private class MyDevice {
        private BluetoothGatt deviceGatt;
        private boolean isConnected;

        public MyDevice(BluetoothGatt deviceGatt, boolean isConnected) {
            this.deviceGatt = deviceGatt;
            this.isConnected = isConnected;
        }

        public BluetoothGatt getDeviceGatt() {
            return deviceGatt;
        }

        public boolean getIsConnected() {
            return isConnected;
        }

        public void setDeviceGatt(BluetoothGatt deviceGatt) {
            this.deviceGatt = deviceGatt;
        }

        public void setIsConnected(boolean isConnected) {
            this.isConnected = isConnected;
        }
    }

    /**
     * get the gatts of connect device
     *
     * @return gatts
     */
    private Map<String, MyDevice> getGattMap() {
        return gattMap;
    }

    /**
     * Device is connect?
     *
     * @param address device's address
     * @return if true : device is connect. if not device is disconnect.
     */
    public boolean isConnected(final String address) {
        MyDevice device = gattMap.get(address);
        if (device != null && device.getDeviceGatt() != null) {
            return device.getIsConnected();
        } else {
            return false;
        }
        /*
        BluetoothGatt g = getBluetoothGatt(address);
        if (g == null) {
            return false;
        }
        BluetoothDevice d = g.getDevice();
        if (d == null) {
            return false;
        }
        int connectionState = mBluetoothManager.getConnectionState(d,
                BluetoothProfile.GATT);
        return connectionState == BluetoothProfile.STATE_CONNECTED;*/
    }

    /**
     * clear the device
     */
    private void clearBond() {
        //get BluetoothAdapter，this API stand by android 2.0
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        Set<BluetoothDevice> devices = adapter.getBondedDevices();
        Log.e(TAG, "clearBond , devices.size:" + devices.size());
        if (!devices.isEmpty()) {
            for (BluetoothDevice device : devices) {
                if (device != null) {
                    Log.e(TAG, "clearBond: device address : " + device.getAddress() + ", device name : " + device.getName());
                    if (gattMap.get(device.getAddress()) == null) {
                        unpairDevice(device);
                    }
                }
            }
        }
    }

    private void unpairDevice(BluetoothDevice device) {
        try {
            Method m = device.getClass()
                    .getMethod("removeBond", (Class[]) null);
            m.invoke(device, (Object[]) null);
            Log.e(TAG, "clearBond unpairDevice : " + device.getAddress());
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }
    }

    public void removeWaitConnectDevice(String address) {
        waitConnectMap.remove(address);
    }

    public void cleanWaitConnectDevice() {
        waitConnectMap.clear();
    }

    /**
     * get the device
     *
     * @return device
     */
    public BluetoothDevice getBluetoothDevice(String address) {
        if (!TextUtils.isEmpty(address)) {
            BluetoothGatt gatt = getBluetoothGatt(address);
            if (gatt != null) {
                return gatt.getDevice();
            }
        }
        return null;
    }

    /**
     * get the gatt of device
     *
     * @return gatt
     */
    public BluetoothGatt getBluetoothGatt(String address) {
        if (!TextUtils.isEmpty(address)) {
            MyDevice device = gattMap.get(address);
            if (device != null) {
                return device.getDeviceGatt();
            }
            return null;
        }
        return null;
    }

    private void connectBroadcastUpdate(final String action, final String address,
                                 final int status) {
        final Intent intent = new Intent(action);
        intent.putExtra(EXTRA_ADDRESS, address);
        intent.putExtra(EXTRA_STATUS, status);
        if (mContext != null) {
            mContext.sendBroadcast(intent);
        }
    }

    private void wrBroadcastUpdate(final String action,
                                 final BluetoothGattCharacteristic characteristic, final int status, final String address) {
        final Intent intent = new Intent(action);
        intent.putExtra(EXTRA_ADDRESS, address);
        intent.putExtra(EXTRA_UUID, characteristic.getUuid().toString());
        intent.putExtra(EXTRA_DATA, characteristic.getValue());
        intent.putExtra(EXTRA_STATUS, status);
        if (mContext != null) {
            mContext.sendBroadcast(intent);
        }
        mBusy = false;
        //Log.e("test1", "broadcastUpdate characteristic :"+mBusy + "   "  + characteristic.getUuid());
        //connectInMap();
    }

    private boolean checkGatt() {
        if (mBtAdapter == null) {
            Log.e(TAG, "BluetoothAdapter not initialized");
            return false;
        }
        if (mBusy) {
            Log.e(TAG, "manager busy");
            return false;
        }
        return true;
    }

    /**
     * Retrieves a service of supported GATT services on the connected device.
     *
     * @param servUUID Service's uuid
     * @param address  Device's address
     * @return BluetoothGattService of supported service.
     */
    public BluetoothGattService getGattService(UUID servUUID, String address) {
        if (TextUtils.isEmpty(address)) {
            return null;
        }
        for (BluetoothGattService s : getSupportedGattServices(address)) {
            if (s.getUuid().equals(servUUID)) {
                return s;
            }
        }
        return null;
    }

    /**
     * Initializes a reference to the local Bluetooth adapter.
     *
     * @return Return true if the initialization is successful.
     */
    public boolean initialize(Context context) {
        // For API level 18 and above, get a reference to BluetoothAdapter through
        // BluetoothManager.
        if (context == null) {
            Log.e(TAG, "initialize bluetooth manager false; context is null.");
            return false;
        }
        mContext = context;
//        mThis = this;
        if (mBluetoothManager == null) {
            mBluetoothManager = (android.bluetooth.BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
            if (mBluetoothManager == null) {
                Log.e(TAG, "Unable to initialize BluetoothManager.");
                return false;
            }
        }

        mBtAdapter = mBluetoothManager.getAdapter();
        if (mBtAdapter == null) {
            Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
            return false;
        }
        return true;
    }

    /**
     * restart bluetooth adapter.
     */
    public void restartBtAdapter() {
        if (mBtAdapter == null) {
            mBtAdapter = mBluetoothManager.getAdapter();
            if (mBtAdapter == null) {
                Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
                return;
            }
        }
        waitConnectMap.clear();
        gattMap.clear();
        mBtAdapter.disable();
        try {
            Thread.sleep(2500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        mBtAdapter.enable();
    }
    //
    // GATT API
    //

    /**
     * Retrieves the number of GATT services on the connected device. This should
     * be invoked only after {@code BluetoothGatt#discoverServices()} completes
     * successfully.
     *
     * @param address device's address
     * @return A {@code integer} number of supported services.
     */
    public int getNumServices(String address) {
        return getSupportedGattServices(address).size();
    }

    /**
     * Retrieves a list of supported GATT services on the connected device. This
     * should be invoked only after {@code BluetoothGatt#discoverServices()}
     * completes successfully.
     *
     * @param address device's address
     * @return A {@code List} of supported services.
     */
    public List<BluetoothGattService> getSupportedGattServices(String address) {
        BluetoothGatt mBluetoothGatt = getBluetoothGatt(address);
        if (mBluetoothGatt == null) {
            return new ArrayList<BluetoothGattService>();
        }
        return mBluetoothGatt.getServices();
    }

    /**
     * get BluetoothManager
     *
     * @return BluetoothManager
     */
    public static BluetoothManager getInstance() {
        return mThis;
    }

    public boolean waitIdle(int timeout) {
        timeout /= 10;
        while (--timeout > 0) {
            if (mBusy)
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            else
                break;
        }

        return timeout > 0;
    }
    //-----------------  read and write data ------------------------------

    /**
     * Request a read on a given {@code BluetoothGattCharacteristic}. The read
     * result is reported asynchronously through the
     * {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
     * callback.
     *
     * @param address        device's address
     * @param characteristic The characteristic to read from.
     */
    public void readCharacteristic(BluetoothGattCharacteristic characteristic, String address) {
        BluetoothGatt mBluetoothGatt = getBluetoothGatt(address);
        if (mBluetoothGatt == null || characteristic == null) {
            return;
        }
        mBusy = true;
        mBluetoothGatt.readCharacteristic(characteristic);
        /*mBusy = mBluetoothGatt.readCharacteristic(characteristic);*/
        //Log.e("test1", "475 readCharacteristic characteristic :" + mBusy + "   " + characteristic.getUuid());
    }

    /*private ArrayList<BluetoothGattCharacteristic> characteristics;

    private int readCharacteristicsNum = 0;*/

    /**
     * Request some reads on a given {@code BluetoothGattCharacteristic}. The read
     * result is reported asynchronously through the
     * {@code BroadcastReceiver#onReceive(Context context, Intent intent)}
     * callback.
     *
     * @param c       The characteristics to read from.
     * @param address Device's address
     */
    /*public void readCharacteristics(ArrayList<BluetoothGattCharacteristic> c, String address) {
        if (TextUtils.isEmpty(address)) {
            return;
        }
        BluetoothGatt btGatt = getBluetoothGatt(address);
        if (btGatt == null) {
            Log.e(TAG, "readCharacteristic mBtGatt is null");
            return;
        }
        characteristics = c;
        doReadCharacteristics(address);
    }

    private void doReadCharacteristics(String address) {
        if (characteristics.get(readCharacteristicsNum) != null) {
            readCharacteristic(characteristics.get(readCharacteristicsNum), address);
            readCharacteristicsNum++;
            if (readCharacteristicsNum == characteristics.size()) {
                readCharacteristicsNum = 0;
                characteristics = null;
            }
        } else {
            Log.e(TAG, "doReadCharacteristics: read characteristic failed, the readCharacteristic is " + readCharacteristicsNum);
        }
    }*/

    /**
     * Writes a given characteristic and its values to the associated remote device.
     * <p>
     * {@code BroadcastReceiver#onReceive(Context context, Intent intent)}
     * callback.
     *
     * @param characteristic Characteristic to write on the remote device
     * @param address        Device's address
     * @param b              data
     * @return true, if the write operation was initiated successfully
     */
    public boolean writeCharacteristic(
            BluetoothGattCharacteristic characteristic, byte b, String address) {
        if (!checkGatt()) {
            return false;
        }
        BluetoothGatt mBluetoothGatt = getBluetoothGatt(address);
        if (mBluetoothGatt == null || characteristic == null) {
            return false;
        }
        byte[] val = new byte[1];
        val[0] = b;
        characteristic.setValue(val);
         mBusy = true;
        //Log.e("test1", "541 writeCharacteristic characteristic :" + mBusy + "   " + characteristic.getUuid());
        return mBluetoothGatt.writeCharacteristic(characteristic);
        /*mBusy = mBluetoothGatt.writeCharacteristic(characteristic);
        return mBusy;*/
    }

    /**
     * Writes a given characteristic and its values to the associated remote device.
     * <p>
     * {@code BroadcastReceiver#onReceive(Context context, Intent intent)}
     * callback.
     *
     * @param characteristic Characteristic to write on the remote device
     * @param address        Device's address
     * @param b              If true ,Characteristic set value is 1 , if not value is 0
     * @return true, if the write operation was initiated successfully
     */
    public boolean writeCharacteristic(
            BluetoothGattCharacteristic characteristic, boolean b, String address) {
        if (!checkGatt()) {
            return false;
        }
        BluetoothGatt mBluetoothGatt = getBluetoothGatt(address);
        if (mBluetoothGatt == null || characteristic == null) {
            return false;
        }
        byte[] val = new byte[1];

        val[0] = (byte) (b ? 1 : 0);
        characteristic.setValue(val);
        mBusy = true;
        //Log.e("test1", "572 writeCharacteristic characteristic :" + mBusy + "   " + characteristic.getUuid());
        return mBluetoothGatt.writeCharacteristic(characteristic);
        /*mBusy = mBluetoothGatt.writeCharacteristic(characteristic);
        return mBusy;*/
    }

    /**
     * Writes a given characteristic and its values to the associated remote device.
     * <p>
     * {@code BroadcastReceiver#onReceive(Context context, Intent intent)}
     * callback.
     *
     * @param characteristic Characteristic to write on the remote device
     * @param address        Device's address
     * @return true, if the write operation was initiated successfully
     */
    public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic, String address) {
        if (!checkGatt())
            return false;
        BluetoothGatt mBluetoothGatt = getBluetoothGatt(address);
        if (mBluetoothGatt == null || characteristic == null) {
            return false;
        }
        mBusy = true;
        //Log.e("test1", "596 writeCharacteristic characteristic :" + mBusy + "   " + characteristic.getUuid());
        return mBluetoothGatt.writeCharacteristic(characteristic);
        /*mBusy = mBluetoothGatt.writeCharacteristic(characteristic);
        return mBusy;*/
    }

    //----------------------------- service ---------------------------------

    /**
     * This class is used to hold services and characteristics for subscriptions.
     */
    public static class PropertiesService {
        private UUID service, characteristic;

        public PropertiesService(UUID s, UUID d) {
            service = s;
            characteristic = d;
        }

        public UUID getService() {
            return service;
        }

        public UUID getCharacteristic() {
            return characteristic;
        }
    }

    /**
     * Subscribe to interested Bluetooth Characteristic
     *
     * @param enable  Set to true to enable notifications/indications
     * @param address Device's address
     * @param list    Properties service list
     */
    public void subscribeGattCharacteristics(final boolean enable, final String address, final List<PropertiesService> list) {
        //final boolean enable = f;

        if (list == null) {
            return;
        }
        BluetoothGatt btGatt = getBluetoothGatt(address);
        if (btGatt != null) {
            for (PropertiesService device : list) {
                UUID servUuid = device.getService();
                UUID dataUuid = device.getCharacteristic();
                BluetoothGattService serv = btGatt.getService(servUuid);
                if (serv != null) {
                    BluetoothGattCharacteristic charac = serv.getCharacteristic(dataUuid);
                    if (setCharacteristicIndication(charac, enable, address)) {
                        //mBtLeService.waitIdle(GATT_TIMEOUT);
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        //setError(mActivity.getString(R.string.Sensor_notification_failed) + " : " + serv.getUuid().toString());
                        break;
                    }
                }
            }
        }
    }

    /**
     * Discovers services offered by a remote device as well as their
     * characteristics and descriptors.
     *
     * @param address device's address
     */
    public void discoverServices(String address) {
        //mPropertiesServices.clear();
        BluetoothGatt btGatt = getBluetoothGatt(address);
        if (btGatt != null && btGatt.discoverServices()) {
            //mServiceList.clear();
            // setBusy(true);
            Log.e(TAG, "Service discovery started");
        } else {
            Log.e(TAG, "Service discovery start failed");
            //setError(mActivity.getString(R.string.Service_discovery_start_failed));
        }
    }

    /**
     * Enables or disables notification on a give characteristic.
     *
     * @param characteristic Characteristic to act on.
     * @param address        device's address
     * @param enable         Set to true to enable notifications/indications
     * @return true, if the write operation was initiated successfully
     */
    public boolean setCharacteristicNotification(
            BluetoothGattCharacteristic characteristic, boolean enable, String address) {
        if (!checkGatt()) {
            return false;
        }
        boolean ok = false;
        BluetoothGatt mBluetoothGatt = getBluetoothGatt(address);
        if (mBluetoothGatt == null || characteristic == null) {
            return false;
        }
        if (mBluetoothGatt.setCharacteristicNotification(characteristic, enable)) {

            BluetoothGattDescriptor clientConfig = characteristic
                    .getDescriptor(UUID16Attributes.CLIENT_CHARACTERISTIC_CONFIG);
            if (clientConfig != null) {

                if (enable) {
                    ok = clientConfig
                            .setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                } else {
                    ok = clientConfig
                            .setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                }

                if (ok) {
                    mBusy = true;
                    ok = mBluetoothGatt.writeDescriptor(clientConfig);
                    /*mBusy = ok = mBluetoothGatt.writeDescriptor(clientConfig);*/
                    //Log.e("test1", "715 setCharacteristicNotification characteristic :" + mBusy + "   " + characteristic.getUuid());
                }
            }
        }

        return ok;
    }

    /**
     * Enables or disables indication on a give characteristic.
     *
     * @param address        device's address
     * @param enable         Set to true to enable notifications/indications
     * @param characteristic Characteristic to act on.
     * @return true, if the write operation was initiated successfully
     */
    public boolean setCharacteristicIndication(
            BluetoothGattCharacteristic characteristic, boolean enable, String address) {
        if (!checkGatt()) {
            return false;
        }
        boolean ok = false;
        BluetoothGatt mBluetoothGatt = getBluetoothGatt(address);
        if (mBluetoothGatt == null || characteristic == null) {
            return false;
        }
        if (mBluetoothGatt.setCharacteristicNotification(characteristic, enable)) {

            BluetoothGattDescriptor clientConfig = characteristic
                    .getDescriptor(UUID16Attributes.CLIENT_CHARACTERISTIC_CONFIG);
            if (clientConfig != null) {

                if (enable) {
                    ok = clientConfig
                            .setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
                } else {
                    ok = clientConfig
                            .setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                }

                if (ok) {
                    mBusy = true;
                    ok = mBluetoothGatt.writeDescriptor(clientConfig);
                    /*mBusy = ok = mBluetoothGatt.writeDescriptor(clientConfig);*/
                    //Log.e("test1", "759 setCharacteristicIndication characteristic :" + mBusy + "   " + characteristic.getUuid());
                }
            }
        }

        return ok;
    }

    /**
     * enabled value.
     *
     * @param characteristic characteristic
     * @return true, enabled is notification
     */
    public boolean isNotificationEnabled(
            BluetoothGattCharacteristic characteristic) {
        if (!checkGatt() || characteristic == null) {
            return false;
        }

        BluetoothGattDescriptor clientConfig = characteristic
                .getDescriptor(UUID16Attributes.CLIENT_CHARACTERISTIC_CONFIG);
        if (clientConfig == null) {
            return false;
        }

        return clientConfig.getValue() == BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
    }

    //-----------------------------connect bluetooth device-----------------------------

    /**
     * Disconnects an existing connection or cancel a pending connection. The
     * disconnection result is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public void disconnect(String address) {
        if (mBtAdapter == null) {
            Log.e(TAG, "BluetoothAdapter not initialized");
            return;
        }
       /* // isDisconnectAll = false;
        final BluetoothDevice device = mBtAdapter.getRemoteDevice(address);
        int connectionState = mBluetoothManager.getConnectionState(device,
                BluetoothProfile.GATT);*/
        BluetoothGatt mBluetoothGatt = getBluetoothGatt(address);
        if (mBluetoothGatt != null) {
            mBluetoothGatt.disconnect();
        }
    }

    /**
     * Disconnects all existing connection or cancel a pending connection. The
     * disconnection result is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public void disconnectAll() {
        if (mBtAdapter == null) {
            Log.e(TAG, "BluetoothAdapter not initialized");
            return;
        }
        if (gattMap.size() == 0) {
            return;
        }
        for (Map.Entry<String, MyDevice> entry : gattMap.entrySet()) {
            //disconnectNum++;
            MyDevice device = entry.getValue();
            if (device != null && device.getDeviceGatt() != null) {
                device.getDeviceGatt().disconnect();
            }
        }
    }

    public void removeWaitConnectMap(String address) {
        waitConnectMap.remove(address);
    }

    /**
     * After using a given BLE device, the app must call this method to ensure
     * resources are released properly.
     */
    public void close(String address) {
        if (mBtAdapter == null) {
            Log.e(TAG, "BluetoothAdapter not initialized");
            return;
        }
        waitConnectMap.remove(address);
        BluetoothGatt btGatt = getBluetoothGatt(address);
        if (btGatt != null) {
            btGatt.disconnect();
            Log.e(TAG, "close address  : " + address);
            btGatt.close();
            gattMap.remove(address);
        } else {
            Log.e(TAG, "close: Gatt is not on list ,gatt is null");
        }
    }

    /**
     * After using a given BLE device, the app must call this method to ensure
     * resources are released properly.
     */
    public void closeAll() {
        if (mBtAdapter == null) {
            Log.e(TAG, "closeAll failed ,BluetoothAdapter not initialized");
            return;
        }
        waitConnectMap.clear();
        if (gattMap.size() == 0) {
            return;
        }
        int num = 0;
        Log.e(TAG, "close all gattMap : " + gattMap);
        for (Map.Entry<String, MyDevice> entry : gattMap.entrySet()) {
            num++;
            MyDevice device = entry.getValue();
            if (device != null && device.getDeviceGatt() != null) {
                //mBluetoothGatt.disconnect();
                Log.e(TAG, "close all address  : " + entry.getKey());
                device.getDeviceGatt().close();
            }
        }
        if (gattMap.size() == num) {
            gattMap.clear();
        }
    }

    /**
     * Connection timeout timer
     */
    private Handler connectTimeoutHandler = new Handler();
    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            mConnectBusy = false;
            if (connectingDeviceAddress != null) {
                Log.e(TAG, "connect time out ; close " + connectingDeviceAddress);
                close(connectingDeviceAddress);
                connectingDeviceAddress = null;
            }
            stopConnectTimeoutTask();
            connectInMap();
        }
    };
    private String connectingDeviceAddress;

    private void startConnectTimeoutTask(String address) {
        connectingDeviceAddress = address;
        connectTimeoutHandler.postDelayed(runnable, 5000);//Executed once every 5 seconds
    }

    private void stopConnectTimeoutTask() {
        connectTimeoutHandler.removeCallbacks(runnable);
    }

    /**
     * Connects to the GATT server hosted on the Bluetooth LE device.
     *
     * @param device The device address of the destination device.
     * @return Return true if the connection is initiated successfully. The
     * connection result is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public boolean connect(final BluetoothDevice device, boolean autoConnect) {
        if (mBtAdapter == null) {
            Log.e(TAG, "BluetoothAdapter not initialized ");
            return false;
        }
        if (device == null) {
            Log.e(TAG, "Device not found.  Unable to connect.");
            return false;
        }
        if (mConnectBusy && (connectingDeviceAddress == null || !connectingDeviceAddress.equals(device.getAddress()))) {
            waitConnectMap.put(device.getAddress(), new WaitConnectDevice(device, autoConnect));
            Log.e(TAG, "manager is busy , please wait .waitConnectMap is : " + waitConnectMap);
            return false;
        }
        mConnectBusy = true;
        startConnectTimeoutTask(device.getAddress());
        BluetoothGatt g = device.connectGatt(mContext, autoConnect, mGattCallbacks);
        gattMap.put(device.getAddress(), new MyDevice(g, false));
        Log.e(TAG, "connect : " + device.getAddress());
        waitConnectMap.remove(device.getAddress());
        return true;
    }

    /**
     * Connects to the GATT server hosted on the Bluetooth LE device.
     *
     * @param address The device address of the destination device.
     * @return Return true if the connection is initiated successfully. The
     * connection result is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public boolean connect(final String address, boolean autoConnect) {
        if (mBtAdapter == null) {
            Log.e(TAG, "BluetoothAdapter not initialized ");
            return false;
        }
        final BluetoothDevice device = mBtAdapter.getRemoteDevice(address);
        if (device == null) {
            Log.e(TAG, "Device not found.  Unable to connect.");
            return false;
        }
        if (mConnectBusy && (connectingDeviceAddress == null || !connectingDeviceAddress.equals(address))) {
            waitConnectMap.put(device.getAddress(), new WaitConnectDevice(device, autoConnect));
            Log.e(TAG, "manager is busy , please wait .waitConnectMap is : " + waitConnectMap);
            return false;
        }
        mConnectBusy = true;
        startConnectTimeoutTask(device.getAddress());
        BluetoothGatt g = device.connectGatt(mContext, autoConnect, mGattCallbacks);
        gattMap.put(device.getAddress(), new MyDevice(g, false));
        Log.e(TAG, "connect : " + device.getAddress());
        waitConnectMap.remove(device.getAddress());
        return true;
    }
    /*private void test(String string) {
        Log.e("test", "<------------ " + string);
        List<BluetoothDevice> connectedDevices = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
        for (BluetoothDevice connectedDevice : connectedDevices) {
            Log.e("test", "connectedDevice name : " + connectedDevice.getName() + " !  adress :" + connectedDevice.getAddress());
        }
        Log.e("test", string + "------------->gattMap :" + gattMap);
    }*/

   /* /**
     * Connects to the GATT server hosted on the Bluetooth LE devices.
     *
     * @param map The device's map.
     *            {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     *            callback.

    public void connect(Map<String, Boolean> map) {
        if (map.size() > 0) {
            for (Map.Entry<String, Boolean> entry : map.entrySet()) {
                boolean c = entry.getValue();
                String a = entry.getKey();
                waitConnectMap.put(a, c);
            }
            connectInMap();
        }
    }*/

    private void connectInMap() {
        Log.e(TAG, "connectInMap waitConnectMap is : " + waitConnectMap);
        if (waitConnectMap.size() > 0) {
            for (Map.Entry<String, WaitConnectDevice> entry : waitConnectMap.entrySet()) {
                WaitConnectDevice c = entry.getValue();
                //String a = entry.getKey();
                connect(c.getDevice(), c.getAutoConnect());
                break;
            }
        }
    }

    /**
     * GATT client callbacks
     */
    private BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() {

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                            int newState) {
           /* if (gattMap.size() == 0) {
                // Log.e(TAG, "mBluetoothGatt not created!");
                return;
            }*/

            BluetoothDevice device = gatt.getDevice();
            String address = device.getAddress();
            //if (gattMap.get(address) == null) {
            //}
            Log.e(TAG, "onConnectionStateChange (" + address + ") " + newState +
                    " status: " + status);
            //waitConnectMap.remove(address);
            switch (newState) {
                case BluetoothProfile.STATE_CONNECTED:
                    //gattMap.put(address, gatt);
                   /* if (status == BluetoothGatt.GATT_SUCCESS) {
                        gatt.discoverServices();
                    } else {
                        broadcastUpdate(ACTION_GATT_CONNECTED, address, status);
                    }*/
                    updateMyDevice(address, gatt, true);
                    connectBroadcastUpdate(ACTION_GATT_CONNECTED, address, status);
                    mConnectBusy = false;
                    connectingDeviceAddress = null;
                    stopConnectTimeoutTask();
                    connectInMap();
                    //broadcastUpdate(ACTION_GATT_CONNECTED, address, status);
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:
                    updateMyDevice(address, gatt, false);
                    connectBroadcastUpdate(ACTION_GATT_DISCONNECTED, address, status);
                    break;
                default:
                    // Log.e(TAG, "New state not processed: " + newState);
                    break;
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            BluetoothDevice device = gatt.getDevice();
            connectBroadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED, device.getAddress(),
                    status);
            //broadcastUpdate(ACTION_GATT_CONNECTED, device.getAddress(), status);
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            BluetoothDevice device = gatt.getDevice();
            String address = device.getAddress();
            wrBroadcastUpdate(ACTION_DATA_NOTIFY, characteristic,
                    BluetoothGatt.GATT_SUCCESS, address);
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic, int status) {
            BluetoothDevice device = gatt.getDevice();
            String address = device.getAddress();
            wrBroadcastUpdate(ACTION_DATA_READ, characteristic, status, address);
            /*if (characteristics != null) {
                doReadCharacteristics(address);
            }*/
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt,
                                          BluetoothGattCharacteristic characteristic, int status) {
            BluetoothDevice device = gatt.getDevice();
            String address = device.getAddress();
            wrBroadcastUpdate(ACTION_DATA_WRITE, characteristic, status, address);
        }

        @Override
        public void onDescriptorRead(BluetoothGatt gatt,
                                     BluetoothGattDescriptor descriptor, int status) {
            mBusy = false;
            Log.e(TAG, "onDescriptorRead: "+mBusy + "   "  + descriptor.getUuid().toString());
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt,
                                      BluetoothGattDescriptor descriptor, int status) {
            mBusy = false;
            Log.e(TAG, "onDescriptorWrite: "+mBusy + "   "  + descriptor.getUuid().toString());
        }
    };

    private void updateMyDevice(String address, BluetoothGatt gatt, boolean isConnected) {
        MyDevice device = gattMap.get(address);
        if (device != null && device.getDeviceGatt() != null) {
            device.setDeviceGatt(device.getDeviceGatt());
            device.setIsConnected(isConnected);
        } else {
            gattMap.put(address, new MyDevice(gatt, isConnected));
        }
    }
}
